package com.bondar.linkedlist;

import java.util.Iterator;
import java.util.NoSuchElementException;

import com.bondar.exceptions.MyIndexOutOfBoundsException;
import com.bondar.interfaces.MyList;
import com.bondar.interfaces.MyQueue;
import com.bondar.interfaces.MyStack;

public class MyLinkedList implements MyList, MyQueue, MyStack {

	private int size;
	private Node firstElement;
	private Node lastElement;

	public MyLinkedList() {
		size = 0;
		firstElement = null;
		lastElement = null;
	}
	
	public MyLinkedList (MyLinkedList list) {
		addAll(list.toArray());
	}

	@Override
	public String toString() {
		if (firstElement == null) {
			return "";
		}

		Node current = firstElement;
		StringBuilder sb = new StringBuilder();

		while (current.hasNext()) {
			sb.append(current.getElement() + "->");
			current = current.next();
		}

		return sb.append(current.getElement()).toString();
	}

	@Override
	public void add(Object element) {
		if (firstElement == null) {
			firstElement = new Node(element);
			lastElement = firstElement;
		} else {
			Node newElement = new Node(element);
			lastElement.next = newElement;
			lastElement = newElement;
		}

		++size;
	}

	@Override
	public void add(int index, Object element) throws MyIndexOutOfBoundsException {
		if ((index > size || index < 0) && index != 0) {
			throw new MyIndexOutOfBoundsException();
		}

		Node newElement = new Node(element);
		Node current = firstElement;

		// add to head of list?
		if (index == 0) {
			if (current != null) {
				newElement.next = firstElement;
				firstElement = newElement;
			} else {
				firstElement = lastElement = newElement;
			}
		} else {
			// get previous element
			for (int i = 0; i < index - 1; i++) {
				current = current.next();
			}

			newElement.next = current.next();
			current.next = newElement;
		}

		++size;
	}

	@Override
	public void addAll(Object[] arr) {
		if (arr == null) {
			throw new NullPointerException();
		}
		
		for (int i = 0; i < arr.length; i++) {
			add(arr[i]);
		}
	}

	@Override
	public void addAll(int index, Object[] arr)
			throws IndexOutOfBoundsException {
		if ((index > size - 1 || index < 0) && index != 0) {
			throw new MyIndexOutOfBoundsException();
		}
		if (arr == null) {
			throw new NullPointerException();
		}
		
		Node currentElement = new Node(arr[0]);
		Node start = currentElement;
		Node end;

		for (int i = 1; i < arr.length; i++) {
			Node newElement = new Node(arr[i]);
			currentElement.next = newElement;
			currentElement = newElement;
		}

		end = currentElement;

		// it is head of the list?
		if (index == 0) {
			if (firstElement == null) {
				firstElement = start;
				lastElement = end;
			} else {
				end.next = firstElement;
				firstElement = start;
			}
		}
		//
		else {
			currentElement = firstElement;
			for (int i = 0; i < index - 1; i++) {
				currentElement = currentElement.next();
			}

			end.next = currentElement.next();
			currentElement.next = start;
		}

		size += arr.length;
	}

	@Override
	public Object get(int index) throws MyIndexOutOfBoundsException {
		if (index > size - 1 || index < 0) {
			throw new MyIndexOutOfBoundsException();
		}
		
		Node current = firstElement;
		for (int i = 0; i < index; i++) {
			current = current.next();
		}

		return current.getElement();
	}

	@Override
	public Object remove(int index) throws MyIndexOutOfBoundsException {
		if (index > size - 1 || index < 0) {
			throw new MyIndexOutOfBoundsException();
		}
		
		Object data = null;

		Node current = firstElement;

		// list contain only one element
		if (current.hasNext() == false) {
			data = firstElement.getElement();
			firstElement.remove();
			firstElement = lastElement = null;
			--size;
			return data;
		}
		// it is first element?
		if (index == 0) {
			firstElement = current.next();
			data = current.getElement();
			current.remove();
		} else {
			// get pre-last element
			for (int i = 0; i < index - 1; i++) {
				current = current.next();
			}

			Node removedElement = current.next();
			data = removedElement.getElement();

			// it is last element?
			if (index == size - 1) {
				lastElement = current;
			}

			current.next = removedElement.next();
			removedElement.remove();
		}
		size = size - 1 < 0 ? 0 : size - 1;

		return data;
	}

	@Override
	public void set(int index, Object element) throws MyIndexOutOfBoundsException {
		if (index > size || index < 0 || size == 0) {
			throw new MyIndexOutOfBoundsException();
		}
		
		Node current = firstElement;
		for (int i = 0; i < index; i++) {
			current = current.next();
		}

		current.setElement(element);
	}

	@Override
	public int indexOf(Object obj) {
		if (firstElement == null) {
			return -1;
		}
		
		Node current = firstElement;
		for (int i = 0; i < size; i++) {
			if (current.getElement() != null) {
				if (current.getElement().equals(obj))
					return i;
			} else {
				if (current.getElement() == obj) {
					return i;
				}
			}

			current = current.next();
		}
		return -1;
	}

	@Override
	public int size() {
		return size;
	}

	@Override
	public Object[] toArray() {
		Object[] array = new Object[size];

		Node current = firstElement;
		for (int i = 0; i < size; i++) {
			array[i] = current.getElement();
			current = current.next();
		}

		return array;
	}

	@SuppressWarnings("rawtypes")
	public static class Node implements Comparable {
		private Node next;
		private Object data;

		public Node() {}

		public Node(Object value) {
			next = null;
			data = value;
		}

		public boolean hasNext() {
			return (next != null);
		}

		public Node next() {
			return next;
		}

		public void remove() {
			next = null;
			data = null;
		}

		public Object getElement() {
			return data;
		}

		public void setElement(Object e) {
			data = e;
		}

		@Override
		public int compareTo(Object that) {
			Node entry = (Node) that;
			int result = data.toString().compareTo(entry.toString());
			return result;
		}
		
		@Override
		public boolean equals(Object obj) {
			return super.equals(obj);
		}
		
		@Override
		public int hashCode() {
			return super.hashCode();
		}

	}

	////////// QUEUE //////////
	@Override
	public void offer(Object element) {
		add(element);
	}

	@Override
	public Object peek() {
		if (firstElement == null) {
			throw new NullPointerException();
		}
		
		return get(0);
	}

	@Override
	public Object poll() {
		if (firstElement == null) {
			throw new NullPointerException();
		}
		
		return remove(0);
	}

	@Override
	public void push(Object element) {
		add(element);
	}

	@Override
	public Object pop() {
		return remove(size - 1);
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Iterator iterator() {
		return new Iterator() {
			Node currentElement = firstElement;

			@Override
			public boolean hasNext() {
				return currentElement != null;
			}

			@Override
			public Object next() {
				Object data = currentElement.getElement();
				currentElement = currentElement.next();
				return data;
			}

			@Override
			public void remove() {
				
			}
		};
	}

	@Override
	public void clear() {
		Node currentElement = firstElement;
		Node deletedElement = null;

		while (currentElement != null) {
			deletedElement = currentElement;
			currentElement = currentElement.next;
			deletedElement.remove();
		}
		size = 0;
	}

	@Override
	public boolean isEmpty() {
		return size == 0;
	}
	
	public void addFirst(Object element) {
		add(0, element);
	}
	
	public void addLast(Object element) {
		add(size, element);
	}
	
	public Object getFirst() throws MyIndexOutOfBoundsException {
		return get(0);
	}
	
	public Object getLast() throws MyIndexOutOfBoundsException {
		return get(size-1);
	}
	
	public Object removeFirst() throws MyIndexOutOfBoundsException {
		return remove(0);
	}
	
	public Object removeLast() throws MyIndexOutOfBoundsException {
		return remove(size-1);
	}
	
	@SuppressWarnings("rawtypes")
	public Iterator descendingIterator() {
		return new Iterator() {
			Object[] array = toArray();
			int index = size-1;

			@Override
			public boolean hasNext() {
				return index >= 0;
			}

			@Override
			public Object next() throws NoSuchElementException {
				if (index<0) {
					throw new NoSuchElementException();
				}
				return array[index--];
			}

			@Override
			public void remove() {
			}
			
		};
	}

	
	
}
